#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

using namespace std;

namespace {

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id (int id) {
  std::unique_lock<std::mutex> lck(mtx);
  // 相当于 cv.wait(lck, []{return ready;});
  while (!ready) cv.wait(lck);
  // ...
  std::cout << "thread " << id << '\n';
}

void go() {
  std::unique_lock<std::mutex> lck(mtx);
  ready = true;
  cv.notify_all();
}

void Handle1 ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_id,i);

  std::cout << "10 threads ready to race...\n";
  go();                       // go!

  for (auto& th : threads) th.join();
}


std::mutex m;
std::condition_variable cvd;
std::string data;
bool rdy = false;
bool processed = false;

void worker_thread()
{
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    // wait函数会先解锁，然后挂起线程等待
    // 被notify后会先尝试加锁，如果加锁失败会继续尝试获得锁(类似于轮询)，而不会再次阻塞。当持有锁的线程释放锁时，这些线程中的一个会获得锁。而其余的会接着尝试获得锁
    cv.wait(lk, []{return rdy;});

    // after the wait, we own the lock.
    std::cout << "Worker thread is processing data\n";
    data += " after processing";

    // Send data back to main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";

    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    // 这里如果先notify后unlock，被唤醒的线程尝试加锁后会失败，要等到该线程解锁才能加锁成功
    lk.unlock();
    cvd.notify_one();
}

void Handle2()
{
    std::thread worker(worker_thread);

    data = "Example data";
    // send data to the worker thread
    {
        std::lock_guard<std::mutex> lk(m);
        rdy = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cvd.notify_one();

    // wait for the worker
    {
        // 可能这里先获取到锁，导致两个线程都处于wait状态
        std::unique_lock<std::mutex> lk(m);
        cvd.wait(lk, []{return processed;});
    }
    std::cout << "Back in main(), data = " << data << '\n';

    worker.join();
}

mutex mt;
condition_variable condv;
// 当两个线程都要用到某一个变量且该变量的值会被改变时，应该用volatile声明，该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。
// 如果变量被装入寄存器，那么两个线程有可能一个使用内存中的变量，一个使用寄存器中的变量，这会造成程序的错误执行。
// volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出，而不是使用已经存在寄存器中的值
volatile bool state = true;

void func(int index, bool needState) {
    for (int i = 0; i < 3; i++) {
        unique_lock<mutex> lck(mt);
        condv.wait(lck, [needState]() {return state == needState; });
        cout << " i am thread " << index << endl;
        state = !state;
        condv.notify_all();
    }
}

void Handle3()
{
    thread th1(func, 1, true);
    thread th2(func, 2, false);
    th1.join();
    th2.join();
}

}

void HandleCondition() {
    // Handle1();
    Handle2();
    // Handle3();
}
